/*
 * Copyright (c) 2023 Huawei Technologies Co., Ltd.
 * openUBMC is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *         http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */

#include <mc/dict.h>

#include <ncsi/ncsi_protocol.h>
#include <ncsi/ncsi_socket.h>

#include "ncsi_over_mctp_standard.h"

namespace dev {

mc::dict get_ncsi_command_ctx(uint8_t package_id, uint8_t channel_id, uint8_t packet_type,
                              uint8_t expect_rsp_packet_type) {
    mc::dict ctx({{"PacketType", packet_type},
                  {"ExpectRspPacketType", expect_rsp_packet_type},
                  {"ChannelId", channel_id},
                  {"PackageId", package_id}});
    return ctx;
}

/**
 * @brief 将NCSI协议中的speed_duplex字段转换为实际链路速度(Mbps)
 * 
 * NCSI协议中的speed_duplex字段是一个4位值，用于表示链路的速度和双工模式。
 * 该字段的编码方式如下：
 * - 0: 未定义/无效
 * - 1-16: 对应不同的链路速度配置
 * 
 * @param speed_mbps NCSI协议中的speed_duplex字段值 (1-16)
 * @return 对应的链路速度值(Mbps)，无效输入返回0
 * 
 * speed数组映射表：
 * 索引0: 10Mbps    (speed_duplex=1)
 * 索引1: 10Mbps    (speed_duplex=2) 
 * 索引2: 100Mbps   (speed_duplex=3)
 * 索引3: 100Mbps   (speed_duplex=4)
 * 索引4: 100Mbps   (speed_duplex=5)
 * 索引5: 1000Mbps  (speed_duplex=6)
 * 索引6: 1000Mbps  (speed_duplex=7)
 * 索引7: 10000Mbps (speed_duplex=8)
 * 索引8: 20000Mbps (speed_duplex=9)
 * 索引9: 25000Mbps (speed_duplex=10)
 * 索引10: 40000Mbps (speed_duplex=11)
 * 索引11: 50000Mbps (speed_duplex=12)
 * 索引12: 100000Mbps(speed_duplex=13)
 * 索引13: 2500Mbps  (speed_duplex=14)
 * 索引14: 0Mbps     (speed_duplex=15, 通常表示link down)
 * 索引15: 0Mbps     (speed_duplex=16, 通常表示link down)
 * 
 * 注意：当链路状态为"link down"时，speed_duplex字段通常为15或16，
 *       对应的speed值为0，表示链路未连接或无效。
 */
uint32_t convert_speed_mbps(uint32_t speed_mbps) {
    // NCSI speed_duplex字段到实际速度的映射表
    // 数组索引对应speed_duplex值-1，因为speed_duplex从1开始
    uint32_t speed[] = {10,    10,    100,   100,   100,    1000, 1000, 10000,
                        20000, 25000, 40000, 50000, 100000, 2500, 0,    0};
    
    // 边界检查：speed_mbps必须在有效范围内 [1, 16]
    // speed_mbps=0表示未定义/无效，speed_mbps>16超出协议范围
    if (speed_mbps == 0 || speed_mbps > sizeof(speed) / sizeof(speed[0])) {
        return 0; // 返回0表示无效的链路速度
    }
    
    // 返回对应的链路速度值，speed_duplex从1开始，所以需要减1作为数组索引
    return speed[speed_mbps-1];
}

bool convert_full_duplex(uint8_t full_duplex) {
    bool full_duplex_flag[] = {false, true, false, false, true, false, true,  true,
                               true,  true, true,  true,  true, true,  false, false};
    if (full_duplex > sizeof(full_duplex_flag) / sizeof(full_duplex_flag[0])) {
        return false;
    }
    return full_duplex_flag[full_duplex];
}

uint8_t convert_link_status(uint8_t link_status) {
    // 0-DOWN, 1-UP, 255-无效值
    switch (link_status) {
    case 0x00:
        return 0;
    case 0x01:
        return 1;
    default:
        return 255;
    }
}

bool ncsi_over_mctp_standard::get_link_status(uint8_t package_id, uint8_t channel_id,
                                              ncsi_link_status_info_t* link_status) {
    mc::dict ctx =
        get_ncsi_command_ctx(package_id, channel_id, GET_LINK_STATUS, GET_LINK_STATUS_RSP);
    std::vector<uint8_t> data = {};
    link_status_rsp*     rsp  = nullptr;
    std::string          rsp_str;
    bool                 ret = request(ctx, data, rsp_str);
    if (!ret) {
        return false;
    }
    if (rsp_str.empty() || rsp_str.size() != GET_LINK_STATUS_RSP_LEN) {
        elog("get link status rsp is empty or size(${rsp_size}) is not equal to ${size}",
             ("rsp_size", rsp_str.size())("size", GET_LINK_STATUS_RSP_LEN));
        return false;
    }
    rsp                      = reinterpret_cast<link_status_rsp*>(rsp_str.data());
    link_status->link_status = convert_link_status(rsp->lnk_sts.link_flag);
    link_status->speed_mbps  = convert_speed_mbps(rsp->lnk_sts.speed_duplex);
    link_status->full_duplex = convert_full_duplex(rsp->lnk_sts.speed_duplex);
    return true;
}

bool ncsi_over_mctp_standard::init_ncsi_channel(uint8_t package_id, uint8_t channel_id) {
    mc::dict ctx =
        get_ncsi_command_ctx(package_id, channel_id, CLEAR_INITIAL_STATE, CLEAR_INITIAL_STATE_RSP);
    std::vector<uint8_t> data = {};
    std::string          rsp_str;
    bool                 ret = request(ctx, data, rsp_str);
    if (!ret) {
        elog("unable to initialize NCSI channel, channel_id: ${channel_id}",
             ("channel_id", channel_id));
        return false;
    }
    return true;
}

bool ncsi_over_mctp_standard::send_test_packet(uint8_t package_id, uint8_t channel_id, uint8_t cmd_code, const std::string& req_data) {
    mc::dict ctx = get_ncsi_command_ctx(package_id, channel_id, cmd_code, 0);
    std::vector<uint8_t> data(req_data.begin(), req_data.end());
    std::string          rsp_str;
    bool                 ret = request(ctx, data, rsp_str);
    if (!ret) {
        elog("unable to send test packet, package_id: ${package_id}, channel_id: ${channel_id}, cmd_code: ${cmd_code}",
             ("package_id", package_id)("channel_id", channel_id)("cmd_code", cmd_code));
        return false;
    }

    if (rsp_str.empty()) {
        elog("test packet response is empty");
        return false;
    }
    
    return true;
}

std::string get_firmware_version_string(get_version_id_rsp* rsp) {
    char buffer[16] = {0};
    snprintf(buffer, sizeof(buffer), "%d.%d.%d.%d", rsp->firmware_ver[0],
             rsp->firmware_ver[1], rsp->firmware_ver[2], rsp->firmware_ver[3]);

    std::string firmware_version(buffer);
    return firmware_version;
}

bool ncsi_over_mctp_standard::get_version_id(uint8_t package_id, uint8_t channel_id,
                                             ncsi_version_id_info_t* version_info) {
    if (version_info == nullptr) {
        return false;
    }

    mc::dict ctx = get_ncsi_command_ctx(package_id, channel_id, GET_VERSION_ID, GET_VERSION_ID_RSP);
    std::vector<uint8_t> data = {};
    get_version_id_rsp*  rsp  = nullptr;
    std::string          rsp_str;
    bool                 ret = request(ctx, data, rsp_str);
    if (!ret) {
        return false;
    }
    if (rsp_str.empty() || rsp_str.size() != GET_VERSION_ID_RSP_LEN) {
        elog("get version id rsp is empty or size(${rsp_size}) is not equal to ${size}",
             ("rsp_size", rsp_str.size())("size", GET_VERSION_ID_RSP_LEN));
        return false;
    }
    rsp = reinterpret_cast<get_version_id_rsp*>(rsp_str.data());
    // 获取版本信息
    version_info->firmware_version = get_firmware_version_string(rsp);

    // 将PCI ID信息转换为16进制字符串格式
    char vid_str[16]  = {0};
    char did_str[16]  = {0};
    char svid_str[16] = {0};
    char sdid_str[16] = {0};

    snprintf(vid_str, sizeof(vid_str), "0x%04x", SHORT_BY_BIG_ENDIAN(rsp->pci_vid));
    snprintf(did_str, sizeof(did_str), "0x%04x", SHORT_BY_BIG_ENDIAN(rsp->pci_did));
    snprintf(svid_str, sizeof(svid_str), "0x%04x", SHORT_BY_BIG_ENDIAN(rsp->pci_svid));
    snprintf(sdid_str, sizeof(sdid_str), "0x%04x", SHORT_BY_BIG_ENDIAN(rsp->pci_ssid));

    version_info->vid  = vid_str;
    version_info->did  = did_str;
    version_info->svid = svid_str;
    version_info->sdid = sdid_str;

    return true;
}

bool ncsi_over_mctp_standard::get_port_metrics(uint8_t package_id, uint8_t channel_id,
                                               ncsi_port_metrics_info_t* port_metrics_info) {
    mc::dict ctx = get_ncsi_command_ctx(package_id, channel_id, GET_CONTROLLER_PACKET_STATISTICS,
                                        GET_CONTROLLER_PACKET_STATISTICS_RSP);
    std::vector<uint8_t>              data = {};
    controller_packet_statistics_rsp* rsp  = nullptr;
    std::string                       rsp_str;
    bool                              ret = request(ctx, data, rsp_str);
    if (!ret) {
        return false;
    }
    if (rsp_str.empty() || rsp_str.size() != GET_CONTROLLER_PACKET_STATISTICS_RSP_LEN) {
        elog("get controller packet statistics rsp is empty or size(${rsp_size}) is not equal to "
             "${size}",
             ("rsp_size", rsp_str.size())("size", GET_CONTROLLER_PACKET_STATISTICS_RSP_LEN));
        return false;
    }
    rsp = reinterpret_cast<controller_packet_statistics_rsp*>(rsp_str.data());
    port_metrics_info->rx_frames                 = mc::ntoh(rsp->total_bytes_received);
    port_metrics_info->tx_frames                 = mc::ntoh(rsp->total_bytes_transmitted);
    port_metrics_info->rx_unicast_frames         = mc::ntoh(rsp->total_unicast_packets_received);
    port_metrics_info->tx_unicast_frames         = mc::ntoh(rsp->total_unicast_packets_transmitted);
    port_metrics_info->rx_multicast_frames       = mc::ntoh(rsp->total_multicast_packets_received);
    port_metrics_info->tx_multicast_frames       = mc::ntoh(rsp->total_multicast_packets_transmitted);
    port_metrics_info->rx_broadcast_frames       = mc::ntoh(rsp->total_broadcast_packets_received);
    port_metrics_info->tx_broadcast_frames       = mc::ntoh(rsp->total_broadcast_packets_transmitted);
    port_metrics_info->rx_fcs_errors             = mc::ntoh(rsp->fcs_received_errors);
    port_metrics_info->rx_under_size_frames      = mc::ntoh(rsp->runt_packets_received);
    port_metrics_info->rx_over_size_frames       = mc::ntoh(rsp->jabber_packets_received);
    port_metrics_info->tx_single_collisions      = mc::ntoh(rsp->single_collision_transmit_frames);
    port_metrics_info->tx_multiple_collisions    = mc::ntoh(rsp->multiple_collision_transmit_frames);
    port_metrics_info->tx_late_collisions        = mc::ntoh(rsp->late_collision_frames);
    port_metrics_info->tx_excessive_collisions   = mc::ntoh(rsp->excessive_collision_frames);
    port_metrics_info->rx_frame_alignment_errors = mc::ntoh(rsp->alignment_errors);

    return true;
}

bool ncsi_over_mctp_standard::get_capabilities(uint8_t package_id, uint8_t channel_id, ncsi_capabilities_info_t *capability_info)
{
    mc::dict ctx = get_ncsi_command_ctx(package_id, channel_id, GET_CAPABILITIES, GET_CAPABILITIES_RSP);
    std::vector<uint8_t> data = {};
    get_capability_rsp*  rsp  = nullptr;
    std::string          rsp_str;
    bool                 ret = request(ctx, data, rsp_str);
    if (!ret) {
        elog("unable to get capabilities, channel_id: ${channel_id}", ("channel_id", channel_id));
        return false;
    }

    if (rsp_str.empty() || rsp_str.size() != GET_CAPABILITIES_RSP_LEN) {
        elog("get capabilities rsp is empty or size(${rsp_size}) is not equal to ${size}",
             ("rsp_size", rsp_str.size())("size", GET_CAPABILITIES_RSP_LEN));
        return false;
    }
    rsp = reinterpret_cast<get_capability_rsp*>(rsp_str.data());
    capability_info->capality_flag = mc::ntoh(rsp->capality_flag);
    capability_info->broadcast_filter_cap = mc::ntoh(rsp->broadcast_filter_cap);
    capability_info->multicast_filter_cap = mc::ntoh(rsp->multicast_filter_cap);
    capability_info->buffer_cap = mc::ntoh(rsp->buffer_cap);
    capability_info->aen_support = mc::ntoh(rsp->aen_support);
    capability_info->vlan_filter_cnt = rsp->vlan_filter_cnt;
    capability_info->mix_filter_cnt = rsp->mix_filter_cnt;
    capability_info->mul_filter_cnt = rsp->mul_filter_cnt;
    capability_info->unicast_filter_cnt = rsp->unicast_filter_cnt;
    capability_info->vlan_mode = rsp->vlan_mode;
    capability_info->channel_cnt = rsp->channel_cnt;
    
    return true;
}

bool ncsi_over_mctp_standard::get_parameters(uint8_t package_id, uint8_t channel_id, ncsi_parameters_info_t *parameters_info)
{
    mc::dict ctx = get_ncsi_command_ctx(package_id, channel_id, GET_PARAMETERS, GET_PARAMETERS_RSP);
    std::vector<uint8_t> data = {};
    get_parameters_rsp*     rsp  = nullptr;
    std::string          rsp_str;
    bool                 ret = request(ctx, data, rsp_str);
    if (!ret) {
        elog("unable to get capabilities, channel_id: ${channel_id}", ("channel_id", channel_id));
        return false;
    }

    if (rsp_str.empty() || rsp_str.size() != GET_PARAMETERS_RSP_LEN) {
        elog("get parameters rsp is empty or size(${rsp_size}) is not equal to ${size}",
             ("rsp_size", rsp_str.size())("size", GET_PARAMETERS_RSP_LEN));
        return false;
    }
    rsp = reinterpret_cast<get_parameters_rsp*>(rsp_str.data());
    parameters_info->mac_cnt = rsp->mac_cnt;
    parameters_info->mac_enable = rsp->mac_enable;
    parameters_info->vlan_cnt = rsp->vlan_cnt;
    parameters_info->vlan_mode = rsp->vlan_mode;
    parameters_info->fc_mode = rsp->fc_mode;
    memcpy(parameters_info->mac, rsp->mac, 6);
    parameters_info->vlan_enable = mc::ntoh(rsp->vlan_enable);
    parameters_info->link_mode = mc::ntoh(rsp->link_mode);
    parameters_info->bc_mode = mc::ntoh(rsp->bc_mode);
    parameters_info->valid_modes = mc::ntoh(rsp->valid_modes);
    parameters_info->aen_mode = mc::ntoh(rsp->aen_mode);
    parameters_info->vlan = mc::ntoh(rsp->vlan);

    return true;
}

} // namespace dev
